media_pp\elements\source\compositor/
video_layer.rs1use thiserror::Error as ThisError;
9
10pub(crate) const MAX_DIMENSION: u32 = 16_384;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct VideoInputId(pub(crate) u64);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct VideoRect {
22 pub x: i32,
23 pub y: i32,
24 pub width: u32,
25 pub height: u32,
26}
27
28impl VideoRect {
29 pub const fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
30 Self {
31 x,
32 y,
33 width,
34 height,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum VideoFit {
42 Stretch,
44 Contain,
46 Cover,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct VideoLayer {
53 pub rect: VideoRect,
54 pub z_index: i32,
55 pub opacity: f32,
56 pub visible: bool,
57 pub fit: VideoFit,
58}
59
60impl VideoLayer {
61 pub const fn new(rect: VideoRect) -> Self {
62 Self {
63 rect,
64 z_index: 0,
65 opacity: 1.0,
66 visible: true,
67 fit: VideoFit::Contain,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, ThisError)]
78pub(crate) enum VideoLayerError {
79 #[error(
80 "invalid layer dimensions {width}x{height}; each dimension must be 1..={MAX_DIMENSION}"
81 )]
82 InvalidDimensions { width: u32, height: u32 },
83
84 #[error("layer opacity must be finite and between 0.0 and 1.0, got {0}")]
85 InvalidOpacity(f32),
86
87 #[error("input frame has invalid dimensions {width}x{height}")]
88 InvalidInputDimensions { width: u32, height: u32 },
89
90 #[error("scaled layer would exceed {MAX_DIMENSION}px: {width}x{height}")]
91 ScaledLayerTooLarge { width: u32, height: u32 },
92}
93
94pub(crate) fn validate_layer(layer: VideoLayer) -> Result<(), VideoLayerError> {
95 validate_rect(layer.rect)?;
96 validate_opacity(layer.opacity)
97}
98
99pub(crate) fn validate_rect(rect: VideoRect) -> Result<(), VideoLayerError> {
100 if rect.width == 0
101 || rect.height == 0
102 || rect.width > MAX_DIMENSION
103 || rect.height > MAX_DIMENSION
104 {
105 Err(VideoLayerError::InvalidDimensions {
106 width: rect.width,
107 height: rect.height,
108 })
109 } else {
110 Ok(())
111 }
112}
113
114pub(crate) fn validate_opacity(opacity: f32) -> Result<(), VideoLayerError> {
115 if opacity.is_finite() && (0.0..=1.0).contains(&opacity) {
116 Ok(())
117 } else {
118 Err(VideoLayerError::InvalidOpacity(opacity))
119 }
120}
121
122#[derive(Debug, Clone, Copy)]
123pub(crate) struct LayerGeometry {
124 pub(crate) image_x: i64,
125 pub(crate) image_y: i64,
126 pub(crate) image_width: u32,
127 pub(crate) image_height: u32,
128 pub(crate) clip: VideoRect,
129}
130
131pub(crate) fn layer_geometry(
136 source_width: u32,
137 source_height: u32,
138 rect: VideoRect,
139 fit: VideoFit,
140) -> Result<LayerGeometry, VideoLayerError> {
141 if source_width == 0 || source_height == 0 {
142 return Err(VideoLayerError::InvalidInputDimensions {
143 width: source_width,
144 height: source_height,
145 });
146 }
147
148 let source_is_wider = u128::from(rect.width) * u128::from(source_height)
149 <= u128::from(rect.height) * u128::from(source_width);
150 let (image_width, image_height) = match fit {
151 VideoFit::Stretch => (rect.width, rect.height),
152 VideoFit::Contain if source_is_wider => (
153 rect.width,
154 scaled_dimension(source_height, rect.width, source_width),
155 ),
156 VideoFit::Contain => (
157 scaled_dimension(source_width, rect.height, source_height),
158 rect.height,
159 ),
160 VideoFit::Cover if source_is_wider => (
161 scaled_dimension(source_width, rect.height, source_height),
162 rect.height,
163 ),
164 VideoFit::Cover => (
165 rect.width,
166 scaled_dimension(source_height, rect.width, source_width),
167 ),
168 };
169 if image_width > MAX_DIMENSION || image_height > MAX_DIMENSION {
170 return Err(VideoLayerError::ScaledLayerTooLarge {
171 width: image_width,
172 height: image_height,
173 });
174 }
175
176 Ok(LayerGeometry {
177 image_x: i64::from(rect.x) + (i64::from(rect.width) - i64::from(image_width)) / 2,
178 image_y: i64::from(rect.y) + (i64::from(rect.height) - i64::from(image_height)) / 2,
179 image_width,
180 image_height,
181 clip: rect,
182 })
183}
184
185pub(crate) fn scaled_dimension(source: u32, target: u32, divisor: u32) -> u32 {
186 let scaled =
187 (u128::from(source) * u128::from(target) + u128::from(divisor) / 2) / u128::from(divisor);
188 scaled.max(1).min(u128::from(u32::MAX)) as u32
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn contain_and_cover_preserve_aspect_ratio() {
197 let rect = VideoRect::new(10, 20, 100, 100);
198 let contain = layer_geometry(160, 90, rect, VideoFit::Contain).unwrap();
199 assert_eq!((contain.image_width, contain.image_height), (100, 56));
200 assert_eq!((contain.image_x, contain.image_y), (10, 42));
201
202 let cover = layer_geometry(160, 90, rect, VideoFit::Cover).unwrap();
203 assert_eq!((cover.image_width, cover.image_height), (178, 100));
204 assert_eq!((cover.image_x, cover.image_y), (-29, 20));
205 }
206}